﻿#region COPYRIGHT
//--------------------------------------------------------------------------------
// <copyright file="TextureGenerator.cs" company="starLiGHT Entertainment Studios">
//    Copyright (c) 2007, 2008, 2009, 2010
//       Roland Rosenkranz (Glatzemann@email.de)
// </copyright>
// <license>
//   This file is part of starLiGHT.Render.
//
//   starLiGHT.Render is free software: you can redistribute it and/or modify
//   it under the terms of the GNU Lesser General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   starLiGHT.Core is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU Lesser General Public License for more details.
//
//   You should have received a copy of the GNU Lesser General Public License
//   along with starLiGHT.Render. If not, see http://www.gnu.org/licenses/.
//    
//   ADDITIONAL (commercial) LICENSES for starLiGHT.Render are available on request.
// </license>
// <version>
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev:: 620                   $:  Revision of last commit *
// * $Author:: unknown            $:  Author of last commit   *
// * $Date:: 2010-12-07 15:23:58 #$:  Date of last commit     *
// ************************************************************
// </version>
//--------------------------------------------------------------------------------
#endregion

namespace starLiGHT.Render
{
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using starLiGHT.Collections;

    #endregion

    public class TextureSurface : IDisposable
    {
        public Texture2D texture;
        public int width;
        public int height;
        private Color[] surfaceColorArray;
        private bool started;
        private bool changed;

        private TextureSurface() 
        { 
        }

        internal TextureSurface(int width, int height)
        {
            this.width = width;
            this.height = height;
            this.surfaceColorArray = new Color[width * height];
            this.changed = true;
        }

        public Color[] SurfaceColorArray
        {
            get { return this.surfaceColorArray; }
            set { this.surfaceColorArray = value; }
        }

        public bool Started
        {
            get { return this.started; }
            set { this.started = value; }
        }

        public bool Changed
        {
            get { return this.changed; }
            set { this.changed = value; }
        }

        public void setPixel(int x, int y, Color color)
        {
            if (x < 0 || x >= this.width || y <= 0 || y >= this.height) return;

            this.surfaceColorArray[y + x * this.width] = color;
            this.changed = true;
        }

        public void writeData()
        {
            if (this.changed)
            {
                this.texture.SetData<Color>(this.surfaceColorArray);
                this.changed = false;
            }
        }

        public void Dispose()
        {
            if (this.texture != null)
            {
                this.texture.Dispose();
                this.texture = null;
            }

            this.surfaceColorArray = null;
        }

        public void clear()
        {
            for (int i = 0; i < this.surfaceColorArray.Length; i++)
            {
#if XNA4
                this.surfaceColorArray[i] = Color.Transparent;
#else
                this.surfaceColorArray[i] = Color.TransparentBlack;
#endif
            }

            this.changed = true;
        }
    }

    /// <summary>
    /// The TextureGenerator is used to create textures (e.g. for debugging purposes) through
    /// code. You are able to create basic textures with simple primitives on it for example.
    /// </summary>
    public static class TextureGenerator
    {
#region Member variables
        private static FastList<Texture2D> textures = new FastList<Texture2D>();
        private static FastList<TextureSurface> surfaces = new FastList<TextureSurface>();

#endregion

        public static TextureSurface CreateSurface(GraphicsDevice device, int width, int height, Color fillColor)
        {
            TextureSurface surface = new TextureSurface(width, height);
            surface.texture = CreateTexture(device, width, height, fillColor, false);
            surface.texture.GetData<Color>(surface.SurfaceColorArray);

            return surface;
        }

        public static void RemoveSurface(TextureSurface surface)
        {
            surfaces.Remove(surface);
            surface.SurfaceColorArray = null;
            surface.texture.Dispose();
            surface.texture = null;
            surface = null;
        }

        /// <summary>
        /// Starts a new surface to draw on.
        /// </summary>
        /// <param name="clearSurface"></param>
        /// <param name="surface"></param>
        public static void StartSurface(TextureSurface surface, bool clearSurface)
        {
            if (!surface.Started)
            {
                surface.Started = true;
                if (clearSurface) surface.clear();
            }
            else
            {
                throw new Exception("EndSurface must be called before calling StartSurface because the previous operation was not ended.");
            }
        }

        /// <summary>
        /// Draws an outlined ellipse on a surface.
        /// </summary>
        /// <param name="center">The midpoint of the ellipse.</param>
        /// <param name="radiusX">The width of the ellipse.</param>
        /// <param name="radiusY">The height of the ellipse.</param>
        /// <param name="color">The color of the ellipse.</param>
        /// <remarks>
        /// You have to call <see>StartSurface</see> before you are able to use this
        /// method.
        /// </remarks>
        public static void DrawEllipse(TextureSurface surface, Vector2 center, float radiusX, float radiusY, Color color)
        {
            if (surface.Started)
            {
                int x0 = (int)center.X;
                int y0 = (int)center.Y;
                int rx2 = (int)(radiusX * radiusX);
                int ry2 = (int)(radiusY * radiusY);
                int f = (int)Math.Round(ry2 - rx2 * ry2 + 0.25f * rx2);
                int ddfX = 0;
                int ddfY = 2 * rx2 * ry2;
                int x = 0;
                int y = (int)radiusY;

                surface.setPixel(x + x0, y + y0, color);

                while (ddfX < ddfY)
                {
                    if (f >= 0)
                    {
                        y -= 1;
                        ddfY -= 2 * rx2;
                        f -= ddfY;
                    }

                    x += 1;
                    ddfX += 2 * ry2;
                    f += ddfX + ry2;
                    surface.setPixel(x + x0, y + y0, color);
                }

                f = (int)Math.Round(ry2 * (x + 0.5) * (x + 0.5) + rx2 * (y - 1) * (y - 1) - rx2 * ry2);
                while (y > 0)
                {
                    if (f <= 0)
                    {
                        x += 1;
                        ddfX += 2 * ry2;
                        f += ddfX;
                    }

                    y -= 1;
                    ddfY -= 2 * rx2;
                    f += rx2 - ddfY;
                    surface.setPixel(x + x0, y + y0, color);
                }
            }
            else
            {
                throw new Exception("StartSurface must be called before drawing on a surface.");
            }
        }

        /// <summary>
        /// Draws an outlined circle on a surface.
        /// </summary>
        /// <param name="center">The midpoint of the circle.</param>
        /// <param name="radius">The desired radius of the circle.</param>
        /// <param name="color">The color for drawing the circle.</param>
        /// <param name="surface"></param>
        /// <remarks>
        /// You have to call <see>StartSurface</see> before you are able to use this
        /// method.
        /// </remarks>
        public static void DrawCircle(TextureSurface surface, Vector2 center, float radius, Color color)
        {
            if (surface.Started)
            {
                int xp = (int)center.X;
                int yp = (int)center.Y;
                int balance = -(int)radius;
                int xoff = 0;
                int yoff = (int)radius;

                while (xoff <= yoff)
                {
                    surface.setPixel(xp + xoff, yp + yoff, color);
                    surface.setPixel(xp - xoff, yp + yoff, color);
                    surface.setPixel(xp - xoff, yp - yoff, color);
                    surface.setPixel(xp + xoff, yp - yoff, color);
                    surface.setPixel(xp + yoff, yp + xoff, color);
                    surface.setPixel(xp - yoff, yp + xoff, color);
                    surface.setPixel(xp - yoff, yp - xoff, color);
                    surface.setPixel(xp + yoff, yp - xoff, color);

                    if ((balance += xoff++ + xoff) >= 0)
                    {
                        balance -= --yoff + yoff;
                    }
                }
            }
            else
            {
                throw new Exception("StartSurface must be called before drawing on a surface.");
            }
        }

        /// <summary>
        /// Draws an outline of a rectangle.
        /// </summary>
        /// <param name="color"></param>
        /// <param name="height"></param>
        /// <param name="surface"></param>
        /// <param name="topLeft"></param>
        /// <param name="width"></param>
        /// <remarks>
        /// You have to call <see>StartSurface</see> before you are able to use this
        /// method.
        /// </remarks>
        public static void DrawRectangle(TextureSurface surface, Vector2 topLeft, float width, float height, Color color)
        {
            DrawRectangle(surface, new RectangleF(topLeft, width, height), color);
        }

        /// <summary>
        /// Draws an outline of a rectangle.
        /// </summary>
        /// <remarks>
        /// You have to call <see>StartSurface</see> before you are able to use this
        /// method.
        /// </remarks>
        public static void DrawRectangle(TextureSurface surface, Rectangle rect, Color color)
        {
            DrawRectangle(surface, new RectangleF(rect), color);
        }

        /// <summary>
        /// Draws an outline of a rectangle.
        /// </summary>
        /// <remarks>
        /// You have to call <see>StartSurface</see> before you are able to use this
        /// method.
        /// </remarks>
        public static void DrawRectangle(TextureSurface surface, RectangleF rect, Color color)
        {
            if (surface.Started)
            {
                DrawLine(surface, rect.TopLeft, rect.TopRight, color);
                DrawLine(surface, rect.TopRight, rect.BottomRight, color);
                DrawLine(surface, rect.BottomRight, rect.BottomLeft, color);
                DrawLine(surface, rect.BottomLeft, rect.TopLeft, color);
            }
            else
            {
                throw new Exception("StartSurface must be called before drawing on a surface.");
            }
        }

        /// <summary>
        /// Draws a line from start point to end point.
        /// </summary>
        /// <param name="start">The position of the line's start.</param>
        /// <param name="end">The position of the line's end.</param>
        /// <param name="color">The color which should be used to draw the line.</param>
        public static void DrawLine(TextureSurface surface, Vector2 start, Vector2 end, Color color)
        {
            if (surface.Started)
            {
                bool steep = Math.Abs(end.Y - start.Y) > Math.Abs(end.X - start.X);
                if (steep)
                {
                    start = new Vector2(start.Y, start.X);
                    end = new Vector2(end.Y, end.X);
                }

                if (start.X > end.X)
                {
                    float temp = start.X; start.X = end.X; end.X = temp;
                    temp = start.Y; start.Y = end.Y; end.Y = temp;
                }

                int deltax = (int)(end.X - start.X);
                int deltay = (int)Math.Abs(end.Y - start.Y);
                int error = (int)(deltax * 0.5f);
                int ystep = (start.Y < end.Y) ? 1 : -1;
                int y = (int)start.Y;

                for (int x = (int)start.X; x < (int)end.X; x++)
                {
                    if (steep)
                    {
                        surface.setPixel(x, y, color); ////  ColorArray[y + x * surfaceTexture.Width] = color;
                    }
                    else
                    {
                        surface.setPixel(y, x, color); //// surfaceColorArray[x + y * surfaceTexture.Width] = color;
                    }

                    error = error - deltay;
                    if (error < 0)
                    {
                        y = y + ystep;
                        error = error + deltax;
                    }
                }
            }
            else
            {
                throw new Exception("StartSurface must be called before drawing on a surface.");
            }
        }

        /// <summary>
        /// Creates a Texture2D from the surface you drawed on.
        /// </summary>
        /// <remarks>
        /// You have to call StartSurface before you are able to call EndSurface.
        /// </remarks>
        /// <param name="surface"></param>
        /// <returns></returns>
        public static Texture2D EndSurface(TextureSurface surface)
        {
            if (surface.Started)
            {
                surface.Started = false;
                surface.writeData();
                return surface.texture;
            }

            throw new Exception("StartSurface must be called beforde EndSurface.");
        }

        public static Texture2D CreateTexture(GraphicsDevice device, int width, int height, Color fillColor) { return CreateTexture(device, width, height, fillColor, true); }

        /// <summary>
        /// Creates a texture with the given dimensions that is filled with a solid color.
        /// </summary>
        /// <param name="manageTexture">If set to true the texture is added to the internal list of textures and destroyed during cleanup.</param>
        /// <param name="device"></param>
        /// <param name="fillColor"></param>
        /// <param name="height"></param>
        /// <param name="width"></param>
        public static Texture2D CreateTexture(GraphicsDevice device, int width, int height, Color fillColor, bool manageTexture)
        {
            if (width <= 0 || height <= 0) return null;

            Texture2D tex = new Texture2D(device, width, height);
            
            Color[] col = new Color[width * height];
            for (int i = 0; i < col.Length; i++)
            {
                col[i] = fillColor;
            }

            tex.SetData<Color>(col);

            if (manageTexture)
            {
                textures.Add(tex);
            }

            return tex;
        }

        /// <summary>
        /// Remove all managed textures and surfaces from the memory.
        /// </summary>
        public static void Cleanup()
        {
            while (textures.Count > 0)
            {
                Texture2D tex = textures[0];
                textures.RemoveAt(0);
                tex.Dispose();
                tex = null;
            }

            while (surfaces.Count > 0)
            {
                surfaces[0].Dispose();
                surfaces.RemoveAt(0);
            }
        }
    }
}
